﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms.Layout;
using System.Windows.Forms;
using System.Drawing;

namespace BGShell.Components
{
    partial class DataTipForm 
    {
        private sealed class DataTipLayoutEngine : LayoutEngine
        {
            private sealed class ColumnInfo
            {
                public int Width { get; set; }
            }

            private const int PopTolerance = 12;

            private readonly ColumnInfo ColExpander;
            private readonly ColumnInfo ColIcon;
            private readonly ColumnInfo ColName;
            private readonly ColumnInfo ColValue;

            private readonly DataTipForm _form;
            private readonly ColumnInfo[] _columns;

            private int _selectedItem = -1;
            private int _firstVisibleItem;

            private int _lineHeight;

            public DataTipLayoutEngine(DataTipForm form)
            {
                ColExpander = new ColumnInfo();
                ColIcon = new ColumnInfo();
                ColName = new ColumnInfo();
                ColValue = new ColumnInfo();

                _form = form;
                _columns = new ColumnInfo[]
                {
                    ColExpander,
                    ColIcon,
                    ColName,
                    ColValue,
                };
            }

            public int LineHeight
            {
                get
                {
                    if (_lineHeight == 0)
                    {
                        _lineHeight = MeasureText("DUMMY").Height;
                    }

                    return _lineHeight;
                }
            }

            public int MaxHeight
            {
                get { return (int)(CurrentScreen.Bounds.Height * 0.25); }
            }

            public int Height
            {
                get { return _form.Height; }
            }

            public int MaxWidth
            {
                get { return (int)(CurrentScreen.Bounds.Width * 0.8); }
            }

            public int Width
            {
                get { return _form.Width; }
            }

            public int ExpandedItemIndex
            {
                get { return _form._expandedItem; }
            }

            public int FirstVisibleItemIndex
            {
                get { return _firstVisibleItem; }
                set
                {
                    if (_firstVisibleItem != value)
                    {
                        if (value > (ItemCount - VisibleItemCount))
                        {
                            value = ItemCount - VisibleItemCount;
                        }

                        if (value < 0)
                        {
                            value = 0;
                        }

                        _firstVisibleItem = value;

                        _form.PerformLayout();
                        _form.Invalidate();
                    }
                }
            }

            public int SelectedItemIndex
            {
                get
                {
                    return _selectedItem;
                }
                set
                {
                    if (value != _selectedItem)
                    {
                        if (!IsScrollerItem(value) && (value >= 0))
                        {
                            if (FirstVisibleItemIndex > value)
                            {
                                FirstVisibleItemIndex = value;
                            }
                            else if (FirstVisibleItemIndex < (value - VisibleItemCount + 1))
                            {
                                FirstVisibleItemIndex = value - VisibleItemCount + 1;
                            }
                        }

                        if (IsScrollerItem(value) || ((value >= 0) && (value < ItemCount)))
                        {
                            _selectedItem = value;

                            _form.HideChild();
                            _form.PerformLayout();
                            _form.Invalidate();
                        }
                    }
                }
            }

            public int VisibleItemCount
            {
                get;
                private set;
            }

            public override bool Layout(object container, LayoutEventArgs layoutEventArgs)
            {
                if (container == _form)
                {
                    var height = LineHeight * ItemCount;

                    if (height > MaxHeight)
                    {
                        VisibleItemCount = (int)Math.Floor((double)(MaxHeight / LineHeight));
                        height = (VisibleItemCount * LineHeight) + (2 * ScrollerHeight);
                    }
                    else
                    {
                        VisibleItemCount = ItemCount;
                    }

                    for (int i = FirstVisibleItemIndex; 
                        (i < FirstVisibleItemIndex + VisibleItemCount); 
                        ++i)
                    {
                        LayoutItem(Items[i]);
                    }

                    _form.Size = new Size(CalculateWidth(), _form.Padding.Vertical + height);
                }

                return false;
            }

            private int GetColumnPosition(ColumnInfo col)
            {
                var result = 0;

                foreach (var c in _columns)
                {
                    if (c == col)
                    {
                        break;
                    }

                    result += c.Width;
                }

                return result;
            }

            private int CalculateWidth()
            {
                var result = _form.Padding.Horizontal;

                foreach (ColumnInfo col in _columns)
                {
                    result += col.Width;

                    if (result > MaxWidth)
                    {
                        break;
                    }
                }

                if (result > MaxWidth)
                {
                    _columns[_columns.Length - 1].Width -= _form.Padding.Horizontal + (result - MaxWidth);
                    return MaxWidth;
                }

                return result;
            }

            private void LayoutItem(DataTipItem item)
            {
                UpdateColumn(ColExpander, item.Subitems.Count > 0 ? LineHeight : 0);
                UpdateColumn(ColIcon,     item.HasIcon            ? LineHeight : 0);
                UpdateColumn(ColName,     MeasureText(item.Name).Width);
                UpdateColumn(ColValue,    MeasureText(item.ValueAsString).Width);
            }

            private Size MeasureText(string text)
            {
                return TextRenderer.MeasureText(text, _form.Font);
                //return Size.Add(TextRenderer.MeasureText(text, _form.Font), new Size(5, 2));
            }

            public bool IsTooFarOut(Point pt)
            {
                var screenPt = _form.PointToScreen(pt);
                var nearRect = new Rectangle(_form.Location.X - PopTolerance,
                                             _form.Location.Y - LineHeight,
                                             _form.Size.Width + (2 * PopTolerance),
                                             LineHeight + (int)(_form.Size.Height * 1.5));

                return !nearRect.Contains(screenPt);
            }

            public Rectangle ScrollerUpRect
            {
                get
                {
                    if (IsScrollable)
                    {
                        var rect = ContentRectangle;

                        rect.Height = ScrollerHeight;

                        return rect;
                    }

                    return Rectangle.Empty;
                }
            }

            public Rectangle ScrollerDownRect
            {
                get
                {
                    if (IsScrollable)
                    {
                        var rect = ContentRectangle;
                        
                        rect.Y = rect.Bottom - ScrollerHeight;
                        rect.Height = ScrollerHeight;

                        return rect;
                    }

                    return Rectangle.Empty;
                }
            }

            private Rectangle ContentRectangle
            {
                get
                {
                    var padd = _form.Padding;
                    var width = Width - padd.Horizontal - 1;
                    var height = Height - padd.Vertical - 1;

                    return new Rectangle(padd.Left, padd.Top, width, height);
                }
            }

            public int? GetItemAt(Point pt)
            {
                int y = pt.Y;

                if (_form.ClientRectangle.Contains(pt))
                {
                    if (IsScrollable)
                    {
                        if (ScrollerUpRect.Contains(pt))
                        {
                            return ScrollUpItem;
                        }

                        if (ScrollerDownRect.Contains(pt))
                        {
                            return ScrollDownItem;
                        }

                        y -= ScrollerHeight;
                    }

                    var index = FirstVisibleItemIndex + (y / LineHeight);

                    if ((index >= 0) && (index < FirstVisibleItemIndex + VisibleItemCount))
                    {
                        return index;
                    }
                }

                return null;
            }

            public int ItemCount
            {
                get { return _form._items.Count; }
            }

            public DataTipItemCollection Items
            {
                get { return _form._items; }
            }

            private Screen CurrentScreen
            {
                get
                {
                    if (_form.Owner != null)
                    {
                        return Screen.FromControl(_form.Owner);
                    }

                    return Screen.FromPoint(_form.Location);
                }
            }

            public bool CanScrollUp
            {
                get { return FirstVisibleItemIndex > 0; }
            }

            public bool CanScrollDown
            {
                get { return ((FirstVisibleItemIndex + VisibleItemCount) < ItemCount); }
            }

            public bool IsScrollable
            {
                get { return VisibleItemCount < ItemCount; }
            }

            public IEnumerable<DataTipItem> VisibleItems
            {
                get
                {
                    for (int i = FirstVisibleItemIndex;
                        (i < ItemCount) && (i < FirstVisibleItemIndex + VisibleItemCount);
                        i++)
                    {
                        yield return Items[i];
                    }
                }
            }

            public Point GetChildPopupLocation(int index, Size childSize)
            {
                var rect = GetContentRect(index);
                var pt = _form.PointToScreen(new Point(rect.X, rect.Bottom));

                if ((pt.Y + childSize.Height) > CurrentScreen.Bounds.Bottom)
                {
                    pt.Y -= (childSize.Height + 1);
                }

                if ((pt.X + childSize.Width) > CurrentScreen.Bounds.Right)
                {
                    pt.X = CurrentScreen.Bounds.Right - childSize.Width - 1;
                }

                return pt;
            }

            public Point MiddleLineStart
            {
                get
                {
                    var rect = GetNameRect(FirstVisibleItemIndex);
                    return new Point(rect.Right, rect.Top);
                }
            }

            public Point MiddleLineEnd
            {
                get
                {
                    var rect = GetNameRect(FirstVisibleItemIndex + VisibleItemCount - 1);
                    return new Point(rect.Right, rect.Bottom);
                }
            }

            public Rectangle GetItemRect(int index)
            {
                var padd = _form.Padding;
                var y = padd.Top + (index - FirstVisibleItemIndex) * _lineHeight;

                var width = Width - padd.Horizontal - 1;

                if (IsScrollable)
                {
                    if (index == ScrollUpItem)
                    {
                        return ScrollerUpRect;
                    }

                    if (index == ScrollDownItem)
                    {
                        return ScrollerDownRect;
                    }

                    y += ScrollerHeight;
                }

                return new Rectangle(padd.Left, y, width, LineHeight - 1);
            }

            public Rectangle GetContentRect(int index)
            {
                var rect = GetItemRect(index);
                var offset = GetColumnPosition(ColIcon);
                
                rect.X += offset;
                rect.Width -= offset;

                return rect;
            }

            public Rectangle GetExpanderRect(int index)
            {
                var rect = GetColumnRect(index, ColExpander);

                rect.Offset(4, 4);
                rect.Height -= 6;
                rect.Width -= 6;

                return rect;
            }

            public Rectangle GetIconRect(int index)
            {
                var rect = GetColumnRect(index, ColIcon);

                rect.Offset(1, 1);

                rect.Width -= 2;
                rect.Height -= 2;

                return rect;
            }

            public Rectangle GetNameRect(int index)
            {
                return GetColumnRect(index, ColName);
            }

            public Rectangle GetValueRect(int index)
            {
                return GetColumnRect(index, ColValue);
            }

            private Rectangle GetColumnRect(int index, ColumnInfo col)
            {
                var rect = GetItemRect(index);

                rect.X += GetColumnPosition(col);
                rect.Width = col.Width - 1;

                return rect;
            }

            private static void UpdateColumn(ColumnInfo col, int width)
            {
                if (width > col.Width)
                {
                    col.Width = width;
                }
            }

            private static bool IsScrollerItem(int index)
            {
                return index == ScrollDownItem ||
                    index == ScrollUpItem;
            }
        }
    }
}